
import sign, {
  keyOf
}
  from './sign'

const
  WSP   = '\n    ',
  NLSPR = /\n\x20\x20/g,
  NLWSP = '\n      ',

  N     = sign['-x'],
  P     = sign['+x'],

  STATEMENT  = 0xFFFFF000 & sign.break,
  HIGHEST    = 0xFFFFF000 & sign.ident

function toString (x) {
  return x && x.constructor.name || 'null'
}

class Node {

  constructor () {
    this.x      = 0
    this.loc    = Object.create(null)
  }

  start (start) {
    this.loc.start = start
    return this
  }

  end (end) {
    this.loc.end = end
    return this
  }

  rbp () {
    return HIGHEST
  }

  append () {
    throw Error(
      `unexpected attaching child of ${toString(this)}`
    )
  }

  factor (some) {
    // 变数, 需要进一步追溯来源
    return some && false
  }

  result (some) {
    // 生成
    return some && false
  }

  get items () {
    // 子项
  }

  normalize () {
    throw Error(
      `missing normalize on ${toString(this)}`
    )
  }
}

export class Expression extends Node {}

export class Statement extends Node {
  rbp () {
    return STATEMENT
  }

  get items () {
    return this.body
  }
}

export class Module extends Node {
  constructor (urn) {
    super()
    this.urn      = urn || '<anonymous>'
    this.files    = []
    this.file     = null
    this.pending  = Object.create(null)
  }

  get items () {
    return this.files
  }

  rbp () {
    return 0
  }

  append (x) {
    if (typeof x === 'string') {
      this.file = new File(x)
      this.files.push(this.file)
    }
    else
      this.file.append(x)
  }

  normalize () {
    return this.format(
      this.files.reduce((s, x) => {
        return  s + '\n\n' + x.normalize() + '\n'
      }, `zmodule://${this.urn}`)
    )
  }

  format (s) {
    let
      i = 4,
      lines = s.split('\n'),
      limit = lines.length

    s = lines[0] + '\n'
      + lines[1] + '\n'
      + lines[2] + '\n'
      + lines[3] + '\n'

    while (i < limit) {
      let
        line = lines[i++],
        pos  = line.indexOf('\t')

      if (pos === -1) {
        s += line + '\n'
        continue
      }

      let
        ss = line.split('\t'),
        left = line.charCodeAt(0) === 0x20
          && line.search(/[^\x20]/) || 0

      if (left > 20) { // >= 24
        s += ss.join('') + '\n'
        continue
      }

      let
        first = ss.shift(),
        nsp   = '\n' + ' '.repeat(left),
        wsp   = nsp + '    ',
        c = 80 - left - first.length

      s += first + ss.reduce((sum, s) => {
        let ch = s.charCodeAt(0)

        if (ch === 0x20) {
          c = 76 - left - s.length
          return sum + nsp + '   ' + s
        }

        c -= s.length
        if (c >= 0) return sum + s

        if (ch === 0x27) { // '
          let i = 0, pos = 0, end = s.length
          c += end

          while (pos < end) {
            let cp = s.codePointAt(pos)
            c--
            pos++
            if (cp > 0xFFFF) {
              c--
              pos++
            }
            if (c <= 0) {
              sum += (i && nsp || '')
                + s.substring(i, pos)
              i = pos
              c = 76 - left
            }
          }
          if (i < end)
            sum += nsp + s.substring(i, end)
          c -= end - i
          return sum
        }

        c = 76 - left - s.length

        return sum + wsp + s
      }, '') + '\n'
    }

    return s
  }

}

class File extends Node {
  constructor (path) {
    super()
    this.path   = path || '<anonymous>'
    this.uses   = []
    this.body   = []
    this.notes  = []
  }

  factor (some) {
    return this.uses.some(use => use.factor(some))
  }

  get items () {
    return this.body
  }

  rbp () {
    return 0
  }

  append (x) {
    if (x instanceof Note)
      this.notes.push(x)
    else if (x instanceof Use)
      this.uses.push(x)
    else
      this.body.push(x)
  }

  normalize () {
    let path = this.path

    return 'file://' + path
      + this.uses.reduce((s, x) => {
        return s + '\n\n' + x.normalize()
      }, '')

      + this.body.reduce((s, x) => {
        return s + '\n\n' + x.normalize()
      }, '')
  }
}

export class Use extends Node {
  constructor () {
    super()
    this.imports = []
  }

  factor (some) {
    return this.imports.some(some)
  }

  get items () {
    return this.imports
  }

  rbp () {
    return 0
  }

  append (x) {
    this.imports.push(x)
  }

  normalize () {
    return this.imports.reduce((s, x) => {
      return s + WSP + x.normalize()
    }, 'use')
  }
}

export class Version extends Node {
  constructor (value) {
    super()
    this.value = value
  }

  normalize () {
    let x = this.value
    return x === 1 && '-'
      || x
      && `${x >> 24}.${x >> 8 & 0xFFFF}.${x & 0xFF}`
      || ''
  }
}

export class Import extends Node {
  constructor (path, min, max, id) {
    super()
    this.path = path
    this.min  = min
    this.max  = max
    this.id   = id
    this.hoisting = []
  }

  factor (some) {
    return some(this.id)
      || this.hoisting.some(some)
  }

  append (x) {
    this.hoisting.push(x)
  }

  normalize () {
    let
      id    = this.id.normalize(),
      path  = this.path.value,
      ver = this.min.value !== this.max.value
        && this.min.normalize() + this.max.normalize()
        || this.min.normalize()

    return `'${path}'${ver} ${id}`
      + (
        this.hoisting.length
        && ' [ '
        + this.hoisting.map(x => x.normalize())
          .join('\t, ')
        + ' ]'
        || ''
      )
  }
}

export class Specifier extends Node {
  constructor (name, fork) {
    super()
    this.name = name
    this.fork = fork
  }

  normalize () {
    return this.name + (this.fork && '!' || '')
  }
}

export class External extends Node {
  constructor (module, id) {
    super()
    this.module = module
    this.id     = id
  }

  factor (some) {
    return some(this.module)
  }

  result (some) {
    return some(this.id)
  }

  normalize () {
    return this.module.normalize()
      + '.'
      + this.id.normalize()
  }
}

export class Param extends Node {
  constructor (mold, owner, id) {
    super()
    this.mold   = mold
    this.owner  = owner
    this.id     = id
  }

  factor (some) {
    return some(this.mold)
  }

  result (some) {
    return this.id && some(this.id)
  }

  normalize () {
    return this.mold.normalize()
      + (this.owner && '!' || '')
      + (this.id && ' ' + this.id.normalize() || '')
  }
}

export class Signature extends Node {
  constructor (params, results, yieldable) {
    super()
    this.params  = params
    this.results = results
    this.yieldable = yieldable
  }

  factor (some) {
    return this.params.some(some)
  }

  result (some) {
    return this.results.some(some)
  }

  normalize () {
    let
      params  = this.params.map(x => x.normalize())
        .join('\t, '),
      results = this.results.map(x => x.normalize())
        .join('\t, ')

    return !params && !results && '()'
      || '( \t'
      + (params || '')
      + (
        !results && ' )'
        || (params && '\t, ' || '')
        + (this.yieldable && 'yield ' || 'out ')
        + results
        + ' )'
      )
  }
}

export class Fn extends Node {
  constructor (place, id, signature) {
    super()
    this.place  = place
    this.id     = id
    this.mold   = signature
    this.body   = []
  }

  factor (some) {
    return some(this.mold)
  }

  result (some) {
    return some(this.id)
  }

  get items () {
    return this.body
  }

  rbp () {
    return 0
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return this.place
      + (this.place && ' fn ' || 'fn ')

      + this.id.normalize()
      + ' ' + this.mold.normalize()
      + this.body.reduce((s, x, i) => {
        if (i && x instanceof Closure)
          return s + '\n' + WSP + x.normalize()
            .replace(NLSPR, NLWSP)

        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
  }
}

export class Closure extends Fn {
  constructor (id, mold) {
    super('', id, mold)
  }
  normalize () {
    return super.normalize() + '\n'
  }
}

export class Func extends Fn {
  constructor (place, id, mold) {
    super(place, id, mold)
  }
}

export class Acceptor extends Node {
  constructor (mold, method) {
    super()
    this.mold   = mold
    this.method = method
  }

  factor (some) {
    return some(this.mold)
  }

  result (some) {
    return some(this.method)
  }

  normalize () {
    return this.mold.normalize()
      + (this.method instanceof Overload && '@' || '.')
      + this.method.normalize()
  }
}

export class Method extends Func {
  constructor (place, acceptor, mold) {
    super(place, acceptor, mold)
  }
}

export class Overload extends Node {
  constructor (operator) {
    super()
    this.operator = operator // string
  }

  normalize () {
    return this.operator
  }
}

export class Translator extends Node {
  constructor (method) {
    super()
    this.method = method
  }

  factor (walk) {
    return walk(this.method)
  }
}

export class Constant extends Node {
  constructor (place, translator) {
    super()
    this.place      = place
    this.translator = translator
    this.list = []
  }

  factor (some) {
    return this.translator
      && some(this.translator)
      || false
  }

  get items () {
    return this.list
  }

  rbp () {
    return 0
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return (this.place && this.place + ' ' || '')
      + 'const'
      + (this.translator && ' @' + this.translator() || '')
      + this.list.reduce((s, x) => {
        return s + WSP + x.normalize()
      }, '')
  }
}

export class Pending extends Node {
  constructor (target) {
    super()
    this.target = target
    this.tokens = []
  }

  result (some) {
    return some(this.target)
  }

  get items () {
    return this.tokens
  }

  append (x) {
    this.tokens.push(x)
  }

  normalize () {
    return this.target.normalize()
      + ' ='
      + this.tokens.map(x => ' \t' + x.normalize())
  }
}

export class Sign extends Node {
  constructor (value) {
    super()
    this.value = value
  }

  rbp () {
    return this.value & 0xFFFFF000
  }

  normalize () {
    return keyOf(this.value)
  }
}

export class Enum extends Node {
  // enum id of base
  constructor (place, id, base) {
    super()
    this.place  = place
    this.id     = id
    this.base   = base
    this.list   = []
  }

  factor (some) {
    return this.base && some(this.base) || false
  }

  result (some) {
    return some(this.id)
  }

  get items () {
    return this.list
  }

  rbp () {
    return 0
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return (this.place && this.place + ' ' || '')
      + 'enum ' + this.id.normalize()
      + (this.base && ' of ' + this.base.normalize() || '')
      + this.list.reduce((s, x) => {
        return s + WSP + x.normalize()
      }, '')
  }
}

export class Enumeral extends Node {
  constructor (id, ignore, value) {
    super()
    this.id       = id || null
    this.private  = ignore && true || null
    this.value    = value || null
  }

  factor (some) {
    return this.value && some(this.value) || false
  }

  result (some) {
    return this.id && some(this.id) || false
  }

  normalize () {
    return (this.id && this.id.normalize() || '')
      + (this.private && '_' || '')
      + (this.value && ' : ' + this.value.normalize() || '')
  }
}

export class Type extends Node {
  constructor (place, id, base, traits) {
    super()
    this.place  = place
    this.id     = id
    this.base   = base
    this.traits = traits
    this.fields = []
  }

  factor (some) {
    return this.base && some(this.base)
      || this.traits && some(this.traits)
      || false
  }

  result (some) {
    return some(this.id)
  }

  get items () {
    return this.fields
  }

  rbp () {
    return 0
  }

  append (x) {
    this.fields.push(x)
  }

  normalize () {
    return (this.place && this.place + ' ' || '')
      + 'type ' + this.id.normalize()
      + (
        this.base && (
          this.base instanceof Class && this.base.normalize()
          || this.base instanceof Slots && this.base.normalize()
          || ' extends ' + this.base.normalize()
        )
        || ''
      )
      + (this.traits && ' traits ' + this.traits.normalize() || '')
      + this.fields.reduce((s, x) => {
        return s + WSP + x.normalize()
      }, '\n')
  }
}

export class Field extends Node {
  constructor (id, ignore, nullable, mold) {
    super()
    this.id       = id
    this.private  = ignore || null
    this.nullable = nullable
    this.mold     = mold
  }

  factor (some) {
    return some(this.mold)
  }

  result (some) {
    return this.id && some(this.id)
  }

  normalize () {
    return (this.id && this.id.normalize() || '')
      + (this.private && '_' || '')
      + (this.nullable && '?' || '')
      + ' ' + this.mold.normalize()
  }
}

export class Traits extends Node {
  constructor () {
    super()
    this.list = []
  }

  factor (some) {
    return this.list.some(some)
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return this.list.map(x => x.normalize())
      .join('\t, ')
  }
}

export class Class extends Node {
  // type class of mold, mold
  constructor () {
    super()
    this.list = []
  }

  factor (some) {
    return this.list.some(some)
  }

  rbp () {
    return 0
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return ' of ' + this.list.map(x => x.normalize())
      .join('\t, ')
  }
}

export class Slots extends Node {
  constructor () {
    super()
    this.list = []
  }

  result (some) {
    return this.list.some(some)
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return '<'
      + this.list.map(x => x.normalize()).join(', ')
      + '>'
  }
}

export class Trait extends Node {
  // trait id traits mold, mold
  constructor (place, id, traits) {
    super()
    this.place  = place
    this.id     = id
    this.traits = traits
  }

  factor (some) {
    return this.traits && some(this.traits)
  }

  result (some) {
    return some(this.id)
  }

  rbp () {
    return 0
  }

  normalize () {
    return (this.place && this.place + ' ' || '')
      + 'trait ' + this.id.normalize()
      + (this.traits && ' traits ' + this.traits.normalize() || '')
  }
}

export class Array extends Node {
  constructor (mold, capacity) {
    super()
    this.mold     = mold
    this.capacity = capacity
  }

  normalize () {
    return '['
      + this.mold.normalize()
      + ', ' + this.capacity.normalize()
      + ']'
  }
}

export class Slice extends Node {
  constructor (mold, growing) {
    super()
    this.mold     = mold
    this.growing  = growing || null
  }

  normalize () {
    return '['
      + this.mold.normalize()
      + (this.growing && ', +' + this.growing.normalize() || '')
      + ']'
  }
}

export class List extends Expression {
  constructor (mold) {
    super()
    this.mold     = mold // Slice / Array
    this.elements = []
  }

  get items () {
    return this.elements
  }

  append (x) {
    this.elements.push(x)
  }

  normalize () {
    return this.mold.normalize()
      + '[ '
      + this.elements.map(x => x.normalize())
        .join('\t, ')
      + ' ]'
  }
}

export class Map extends Node {
  constructor () {
    super()
    this.molds = []
  }

  get items () {
    return this.molds
  }

  append (x) {
    this.molds.push(x)
  }

  normalize () {
    return '{ '
      + this.molds.map(x => x.normalize())
        .join('\t, ')
      + ' }'
  }
}

export class Pair extends Node {
  constructor (key, value) {
    super()
    this.key   = key
    this.value = value
  }

  normalize () {
    return this.key.normalize()
      + ' : '
      + this.value.normalize()
  }
}

export class Break extends Statement {
  constructor () {
    super()
  }

  get items () {
    return null
  }

  normalize () {
    return 'break'
  }
}

export class Continue extends Statement {
  constructor () {
    super()
  }

  get items () {
    return null
  }

  normalize () {
    return 'continue'
  }
}

export class Echo extends Statement {
  constructor () {
    super()
    this.list = []
  }

  get items () {
    return this.list
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return 'echo '
      + this.list.map(x => x.normalize())
        .join('\t, ')
  }
}

export class For extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body = []
  }

  factor (walk) {
    return walk(this.clause)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'for ' + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class YieldClause extends Expression {
  constructor (results, clause, size) {
    super()
    this.results  = results
    this.clause   = clause
    this.size     = size
  }

  factor (walk) {
    return walk(this.clause)
  }

  result (walk) {
    return this.results.some(walk)
  }

  normalize () {
    return this.results.map(x => x.normalize())
      .join(', ')
      + ' of '
      + this.clause.normalize()
      + (this.size && ', ' + this.size.normalize() || '')
  }
}

export class Caseclause extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body = []
  }

  factor (walk) {
    return walk(this.clause)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'case ' + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class Ofclause extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body = []
  }

  factor (walk) {
    return walk(this.clause)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'of ' + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class When extends Statement {
  constructor (action, clause) {
    super()
    this.action = action
    this.clause = clause
  }

  factor (some) {
    return some(this.clause)
  }

  get items () {
    return [this.action]
  }

  normalize () {
    return this.action.normalize()
      + ' '
      + this.clause.normalize()
  }
}

export class If extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body   = []
  }

  factor (some) {
    return some(this.clause)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'if '
      + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class Elif extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body   = []
  }

  factor (some) {
    return some(this.clause)
  }

  normalize () {
    return 'elif ' + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class TypeAssertion extends Expression {
  // object, instance of mold
  constructor (object, instance, mold) {
    super()
    this.object   = object
    this.instance = instance || null
    this.mold     = mold
  }

  factor (some) {
    return some(this.operator)
      || some(this.mold)
  }

  result (some) {
    return some(this.instance)
  }

  normalize () {
    return this.object.normalize()
      + (
        this.instance
        && ', ' + this.instance.normalize()
        || ''
      )
      + ' of '
      + this.mold.normalize()
  }
}

export class Switch extends Statement {
  constructor (clause) {
    super()
    this.clause = clause
    this.body   = []
  }

  factor (some) {
    return some(this.clause)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'switch ' + this.clause.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class Else extends Statement {
  constructor () {
    super()
    this.body   = []
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'else'
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class Defer extends Statement {
  constructor () {
    super()
    this.body   = []
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return 'defer'
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }
}

export class Throw extends Statement {
  constructor (clause, message, code, content) {
    super()
    this.clause   = clause
    this.message  = message
    this.code     = code
    this.content  = content
  }

  factor (some) {
    return some(this.clause)
      || some(this.message)
      || this.code && some(this.code)
      || this.content && some(this.content)
  }

  get items () {
    return null
  }

  normalize () {
    return 'throw '
      + this.clause.normalize()
      + '\t , ' + this.message.normalize()
      + (
        this.code
        && '\t , ' + this.code.normalize()
        + (
          this.content
          && '\t , ' + this.content.normalize()
          || ''
        )
        || ''
      )
  }
}

export class Try extends Statement {
  constructor () {
    super()
    this.body = []
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    let
      last = this.body.length - 1,
      x = this.body[last]

    return 'try'
      + this.body.reduce((s, x, i) => {
        if (i === last) return s
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n\n'
      + x.normalize()
  }
}

export class Catch extends Statement {
  constructor (id) {
    super()
    this.id   = id
    this.body = []
  }

  result (some) {
    return some(this.id)
  }

  append (x) {
    this.body.push(x)
  }

  normalize () {
    return '\ncatch ' + this.id.normalize()
      + this.body.reduce((s, x) => {
        return s + WSP + x.normalize()
          .replace(NLSPR, NLWSP)
      }, '\n')
      + '\n'
  }

}

export class Out extends Statement {
  constructor () {
    super()
    this.list = []
  }

  get items () {
    return this.list
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return 'out'
      + (this.list.length && ' ' || '')
      + this.list.map(x => x.normalize())
        .join('\t, ')
  }
}

export class Yield extends Statement {
  constructor () {
    super()
    this.list = []
  }

  get items () {
    return this.list
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return 'yield'
      + (this.list.length && ' ' || '')
      + this.list.map(x => x.normalize())
        .join('\t, ')
  }
}

export class Multivalued extends Statement {
  constructor () {
    super()
    this.targets  = []
    this.operator = 0
    this.sources  = []
  }

  factor (some) {
    return this.sources.some(some)
  }

  result (some) {
    return this.targets.some(some)
  }

  get items () {
    return null
  }

  addTarget (target) {
    this.targets.push(target)
  }

  append (x) {
    this.sources.push(x)
  }

  normalize () {
    return this.targets.map(x => x.normalize())
      .join(', ')
      + ' ' + keyOf(this.operator) + ' '
      + this.sources.map(x => x.normalize())
        .join(', ')
  }
}

export class Assignment extends Statement {
  constructor (target, operator, source) {
    super()
    this.target   = target
    this.operator = operator
    this.source   = source
  }

  factor (some) {
    return some(this.source)
  }

  result (some) {
    return some(this.target)
  }

  get items () {
    return null
  }

  normalize () {
    return this.target.normalize()
      + ' ' + keyOf(this.operator) + ' '
      + this.source.normalize()
  }
}

export class Noop extends Statement {
  normalize () {
    return 'null'
  }
  get items () {
    return null
  }
}

export class Emit extends Statement {
  constructor (callee) {
    super()
    this.callee = callee
    this.arguments = []
  }

  factor (some) {
    return some(this.callee)
      || this.arguments.some(some)
  }

  get items () {
    return null
  }

  append (x) {
    this.arguments.push(x)
  }

  normalize () {
    return this.callee.normalize()
      + (
        !this.arguments.length && ' ()'
        || ' ( '
        + this.arguments.map(x => x.normalize())
          .join('\t, ')
        + ' )'
      )
  }
}

// ++, --
export class Step extends Statement {
  constructor (target, operator) {
    super()
    this.target   = target
    this.operator = operator
  }

  factor (some) {
    return some(this.target)
  }

  result (some) {
    return some(this.target)
  }

  get items () {
    return null
  }

  rbp () {
    return this.operator & 0xFFFFF000
  }

  normalize () {
    return normalize(this.rbp(), realExpr(this.target))
      + keyOf(this.operator)
  }
}

// expression

function realExpr (x) {
  return x instanceof Group
    && x.expression
    || x
}

function normalize (bp, x) {
  // MASK       = 0x04040050
  // LMASK      = 0xFF000000
  // IMASK      = 0x00FF0000
  // CMASK      = 0x0000FFFF

  if (bp < x.rbp())
    return x.normalize()
  return '(\t' + x.normalize() + '\t)'
}

export class Group extends Expression {
  constructor (expression) {
    super()
    this.expression = expression
  }

  factor (some) {
    return some(this.expression)
  }

  normalize () {
    return this.expression.normalize()
  }
}

export class Call extends Expression {
  constructor (callee) {
    super()
    this.callee = callee
    this.arguments = []
  }

  factor (some) {
    return some(this.callee)
      || this.arguments.some(some)
  }

  append (x) {
    this.arguments.push(x)
  }

  toEmit () {
    let x = new Emit(this.callee)
    x.loc = this.loc
    x.arguments = this.arguments
    return x
  }

  normalize () {
    return this.callee.normalize()
      + (
        !this.arguments.length && ' ()'
        || ' ( '
        + this.arguments.map(x => x.normalize())
          .join('\t, ')
        + ' )'
      )
  }
}

// ++, --
export class Update extends Expression {
  constructor (target, operator) {
    super()
    this.target   = target
    this.operator = operator
  }

  factor (some) {
    return some(this.target)
  }

  result (some) {
    return some(this.target)
  }

  rbp () {
    return this.operator & 0xFFFFF000
  }

  normalize () {
    return normalize(this.rbp(), realExpr(this.target))
      + keyOf(this.operator)
  }
}

export class Unary extends Expression {
  constructor (operator, right) {
    super()
    this.operator = operator
    this.right    = right
  }

  factor (some) {
    return some(this.right)
  }

  rbp () {
    return this.operator & 0xFFFFF000
  }

  normalize () {
    return (
      this.operator === N && '-'
      || this.operator === P && '+'
      || keyOf(this.operator)
    ) + '(\t' + this.right.normalize() + '\t)'
  }
}

export class Range extends Expression {
  constructor (left, right) {
    super()
    this.left   = left
    this.right  = right
  }

  factor (some) {
    return some(this.left) || some(this.right)
  }

  normalize () {
    return this.left.normalize() + '..' + this.right.normalize()
  }
}

export class Binary extends Expression {
  constructor (left, operator, right) {
    super()
    this.left = left
    this.operator = operator
    this.right = right
  }

  factor (some) {
    return some(this.left) || some(this.right)
  }

  rbp () {
    return this.operator & 0xFFFFF000
  }

  normalize () {
    let
      left  = realExpr(this.left),
      right = realExpr(this.right),

      l     = left.normalize(),
      k     = keyOf(this.operator),
      r     = right.normalize(),

      lbp   = left.rbp(),
      sbp   = this.rbp(),
      rbp   = right.rbp()

    if (lbp !== HIGHEST && lbp < sbp)
      l = '( ' + l + ' )'

    if (rbp !== HIGHEST && rbp < sbp)
      r = '( ' + r + ' )'

    return l + ' \t' + k + ' ' + r
  }
}

export class Index extends Expression {
  constructor (callee, site) {
    super()
    this.callee = callee
    this.site   = site
  }

  factor (some) {
    return some(this.callee)
      || some(this.site)
  }

  normalize () {
    return this.callee.normalize()
      + '['
      + this.site.normalize()
      + ']'
  }
}

export class Member extends Expression {
  constructor (object, property) {
    super()
    this.object   = object
    this.property = property
  }

  factor (some) {
    return some(this.object)
      || some(this.property)
  }

  normalize () {
    let
      x = realExpr(this.object),
      s = x.normalize()

    return (x.rbp() < HIGHEST && '(' + s + ')' || s)
      + '\t.'
      + this.property.normalize()
  }
}

// factor

export class Template extends Expression {
  constructor () {
    super()
    this.body = []
  }

  append (x) {
    this.body.push(x)
  }
}

export class Quasi extends Node {
  constructor (text) {
    super()
    this.text = text
  }
}

export class Regex extends Expression {
  constructor () {
    super()
  }
}

export class Property extends Node {
  constructor (id, value) {
    super()
    this.id    = id
    this.value = value
  }

  factor (some) {
    return some(this.id)
      || some(this.value)
  }

  normalize () {
    return this.id.normalize()
      + ' : '
      + this.value.normalize()
  }
}

export class Descriptor extends Expression {
  // 临时结构, 需要鉴定合法性
  constructor (mold, style) {
    super()
    this.style  = style
    this.valid  = false // 最终要确定
    this.mold   = mold
    this.list   = []
  }

  factor (some) {
    return some(this.mold)
  }

  get items () {
    return this.list
  }

  append (x) {
    this.list.push(x)
  }

  normalize () {
    return this.mold.normalize()
      + ' ' + this.style[0] + ' \t'
      + this.list.map(x => x.normalize())
        .join('\t, ')
      + ' \t' + this.style[1]
  }
}

export class Structure extends Node {
  // struct{name: value}
  constructor () {
    super()
    this.fields   = []
  }

  get items () {
    return this.fields
  }

  append (x) {
    this.fields.push(x)
  }

  normalize () {
    return 'struct { '
      + this.fields.map(x => x.normalize())
        .join('\t, ')
      + ' }'
  }
}

export class Identifier extends Expression {
  constructor (name) {
    super()
    this.name = name
  }

  normalize () {
    return this.name
  }
}

export class Typeof extends Expression {
  constructor (argument) {
    super()
    this.argument = argument
  }

  factor (some) {
    return some(this.argument)
  }

  normalize () {
    return 'typeof ( ' + this.argument.normalize() + ' )'
  }
}

export class Ignore extends Node {
  normalize () {
    return '_'
  }
}

export class Super extends Expression {
  normalize () {
    return 'super'
  }
}

export class This extends Expression {
  normalize () {
    return 'this'
  }
}

class Literal extends Expression {}

export class Iota extends Literal {
  normalize () {
    return 'iota'
  }
}

export class Sint extends Literal {
  constructor (value) {
    super()
    this.value  = value
  }

  normalize () {
    return this.value.toString()
  }
}

export class Uint extends Literal {
  constructor (value) {
    super()
    this.value  = value
  }

  normalize () {
    return '0x' + this.value.toString(16)
  }
}

export class Null extends Literal {
  normalize () {
    return 'null'
  }
}

export class Bool extends Literal {
  constructor (value) {
    super()
    this.value  = value
  }

  normalize () {
    return this.value.toString()
  }
}


export class Rune extends Literal {
  constructor (value) {
    super()
    this.value  = value
  }

  normalize () {
    return this.value.toString()
  }
}

export class Strings extends Literal {
  constructor (value) {
    super()
    this.value  = value
  }

  normalize () {
    return '\t\''
      + this.value.replace(/\\/g, '\\\\')
        .replace(/'/g, '\\\'')
        .replace(/\n/g, '\\n')
        .replace(/\f/g, '\\f')
        .replace(/\r/g, '\\r')
        .replace(/\t/g, '\\t')
        .replace(/\v/g, '\\v')
      + '\''
  }
}

export class Float extends Literal {
  constructor (combine, fraction, exponent) {
    super()
    this.combine  = combine
    this.fraction = fraction || 0
    this.exponent = exponent || 0
  }

  normalize () {
    let
      sign = this.combine > 3 && '-' || '',
      combine = this.combine & 0b11
    if (combine === 0b11) return 'NaN'
    if (combine  === 0b10) return sign + 'Infinity'

    return sign + combine.toString()
      + (
        !this.fraction && !this.exponent && '.0'
        || (
          this.fraction && '.' + this.fraction.toString() || ''
        )
        + (
          this.exponent && 'E' + this.exponent.toString() || ''
        )
      )
  }
}

export class Note extends Node {
  constructor (text) {
    super()
    this.text = text
  }
}
